pub const sv32 = sv(22, 10, 2);
pub const sv39 = sv(44, 9, 3);
pub const sv48 = sv(44, 9, 4);
pub const sv57 = sv(44, 9, 5);

/// Generates a RISC-V SvX virtual memory system type
pub fn sv(comptime ppn_bit: u16, comptime vpn_bit: u16, comptime vpn_count: usize) type {
    return struct {
        const std = @import("std");
        const is_sv32 = ppn_bit == 22;
        const usize_bit = @bitSizeOf(usize);
        const offset = 12;
        const Offset = u(offset);
        const Ppn = u(ppn_bit);

        fn u(n: u16) type {
            return std.meta.Int(.unsigned, n);
        }
        fn Vpn(n: u16) type {
            return if (n < vpn_count) u(vpn_bit) else u0;
        }

        /// A type capable of representing MAXVA and performing address arithmetic without overflow.
        pub const Addr = if (is_sv32) u64 else usize;
        // Physical integer type: use u34 for Sv32's 34-bit PA, usize for wider modes
        pub const PhyInt = if (is_sv32) u(34) else usize;
        /// Page size calculated from offset bits
        pub const PGSIZE = std.math.maxInt(Offset) + 1;
        /// Number of page table entries per table
        pub const ptes = std.math.pow(usize, 2, vpn_bit);

        /// virtual address
        pub const VirtAddr = packed struct(usize) {
            offset: Offset = 0,
            l0: Vpn(0) = 0,
            l1: Vpn(1) = 0,
            l2: Vpn(2) = 0,
            l3: Vpn(3) = 0,
            l4: Vpn(4) = 0,

            _: u(usize_bit - offset - vpn_bit * vpn_count) = 0, // Reserved upper bits

            // one beyond the highest possible virtual address. For Sv39, MAXVA is actually one bit
            // less than the max allowed, to avoid having to sign-extend virtual addresses that have
            // the high bit set.
            pub const MAXVA: Addr = 1 << (offset + vpn_bit * vpn_count - if (is_sv32) 0 else 1);
            pub const TRAMPOLINE: usize = MAXVA - PGSIZE;
            pub const TRAPFRAME: usize = TRAMPOLINE - PGSIZE;

            /// virtual address 0
            pub const zero: VirtAddr = @bitCast(@as(usize, 0));
            pub const trampoline: VirtAddr = @bitCast(TRAMPOLINE);
            pub const trapframe: VirtAddr = @bitCast(TRAPFRAME);

            /// Extracts the Virtual Page Number(s) for page table walking.
            /// Returns only the active VPN levels in descending order (from high to low level).
            pub fn vpn(self: VirtAddr) [vpn_count]usize {
                const vs: [5]usize = .{ self.l4, self.l3, self.l2, self.l1, self.l0 };
                const r: *const [vpn_count]usize = vs[vs.len - vpn_count ..];
                return r.*;
            }

            pub fn fromInt(int: anytype) VirtAddr {
                const addr: usize = @intCast(int);
                return @bitCast(addr);
            }

            pub fn fromPtr(ptr: anytype) VirtAddr {
                const addr: usize = @intFromPtr(ptr);
                return @bitCast(addr);
            }
        };

        /// physical address
        /// This discards any physical address bits beyond the width of usize. Use with caution if
        /// the system requires full physical addressing (e.g., Sv32 PA > 32 bits).
        pub const PhyAddr = packed struct(usize) {
            offset: Offset = 0,
            ppn: u(bits) = 0,

            _: u(usize_bit - offset - bits) = 0,

            const bits = if (ppn_bit < 27) 20 else ppn_bit;

            /// Extracts the usable physical address as a usize.
            pub fn addr(self: PhyAddr) usize {
                return @bitCast(self);
            }

            /// Converts the physical address to a raw pointer, allowing direct memory access.
            pub fn toPtr(self: PhyAddr) [*]u8 {
                return @ptrFromInt(self.addr());
            }
        };

        /// page-table entry
        pub const Pte = packed struct(usize) {
            // flags
            flags: Flags = .{},

            @"8-9": u2 = 0,

            /// physical page number
            ppn: Ppn = 0,

            _: u(usize_bit - @bitSizeOf(Flags) - 2 - ppn_bit) = 0,

            pub fn getPhyAddr(pte: Pte, off: u12) PhyAddr {
                return .{ .ppn = @truncate(pte.ppn), .offset = off };
            }

            pub const Flags = packed struct(u8) {
                valid: bool = false,
                readable: bool = false,
                writable: bool = false,
                executable: bool = false,
                user: bool = false,
                global: bool = false,
                accessed: bool = false,
                dirty: bool = false,

                const Set = std.bit_set.IntegerBitSet(@bitSizeOf(Flags));

                /// Check whether all bits set in `a` are also set in `b`
                pub fn subsetOf(a: Flags, b: Flags) bool {
                    const aset: Set = @bitCast(a);
                    const bset: Set = @bitCast(b);
                    return aset.subsetOf(bset);
                }

                /// Returns the union of two flags sets (all bits set in either input)
                pub fn join(a: Flags, b: Flags) Flags {
                    var aset: Set = @bitCast(a);
                    const bset: Set = @bitCast(b);
                    aset.setUnion(bset);
                    return @bitCast(aset);
                }
            };
        };
    };
}
